package com.igoal.service;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.LogFactory;
import org.appfuse.service.UserExistsException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.igoal.dao.CityDAO;
import com.igoal.dao.CompanyDAO;
import com.igoal.dao.HobbyDAO;
import com.igoal.dao.PrivacySettingDAO;
import com.igoal.dao.ProfileCompleteDAO;
import com.igoal.dao.RoleDAO;
import com.igoal.dao.SchoolDAO;
import com.igoal.dao.UserCompanyDAO;
import com.igoal.dao.UserDAOExt;
import com.igoal.dao.UserInfoDAO;
import com.igoal.dao.UserRoleDAO;
import com.igoal.dao.UserSchoolDAO;
import com.igoal.dto.CityDTO;
import com.igoal.dto.CompanyDTO;
import com.igoal.dto.HobbyDTO;
import com.igoal.dto.SchoolDTO;
import com.igoal.dto.UserDTO;
import com.igoal.dto.UserInfoDTO;
import com.igoal.entity.ActionTypeEnum;
import com.igoal.entity.Category;
import com.igoal.entity.City;
import com.igoal.entity.Company;
import com.igoal.entity.ComponentEnum;
import com.igoal.entity.Hobby;
import com.igoal.entity.Role;
import com.igoal.entity.School;
import com.igoal.entity.User;
import com.igoal.entity.UserCompany;
import com.igoal.entity.UserInfo;
import com.igoal.entity.UserRole;
import com.igoal.entity.UserSchool;
import com.igoal.entity.factory.EntityFactory;
import com.igoal.utils.CalendarUtils;
import com.igoal.utils.StringUtils;

@Service("userAccountService")
public class UserAccountServiceImpl extends BaseService<UserDTO, User, Long>
		implements UserAccountService {
	@Qualifier("userDAOExt")
	private UserDAOExt userDAOExt;
	private PasswordEncoder passwordEncoder;
	private UserInfoDAO userInfoDAO;
	private RoleDAO roleDAO;
	private HobbyDAO hobbyDAO;
	private ProfileCompleteDAO profileCompleteDAO;
	private SchoolDAO schoolDAO;
	private UserSchoolDAO userSchoolDAO;
	private MailService mailService;
	private UserRoleDAO userRoleDAO;
	private PrivacySettingDAO privacySettingDAO;
	private CityDAO cityDAO;
	private CompanyDAO companyDAO;
	private UserCompanyDAO userCompanyDAO;

	private ActionLoggerService actionLoggerService;

	public UserAccountServiceImpl() {
		logger = LogFactory.getLog(this.getClass());
	}

	@Autowired
	public void setCompanyDAO(CompanyDAO companyDAO) {
		this.companyDAO = companyDAO;
	}

	@Autowired
	public void setUserCompanyDAO(UserCompanyDAO userCompanyDAO) {
		this.userCompanyDAO = userCompanyDAO;
	}

	@Autowired
	public void setCityDAO(CityDAO cityDAO) {
		this.cityDAO = cityDAO;
	}
	
	@Autowired
	public void setHobbyDAO(HobbyDAO hobbyDAO) {
		this.hobbyDAO = hobbyDAO;
	}

	@Autowired
	public void setUserRoleDAO(UserRoleDAO userRoleDAO) {
		this.userRoleDAO = userRoleDAO;
	}

	@Autowired
	public void setUserInfoDAO(UserInfoDAO userInfoDAO) {
		this.userInfoDAO = userInfoDAO;
	}

	@Autowired
	public void setUserDAOExt(UserDAOExt userDAOExt) {
		this.userDAOExt = userDAOExt;
	}

	@Autowired
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	@Autowired
	public void setRoleDAO(RoleDAO roleDAO) {
		this.roleDAO = roleDAO;
	}

	@Autowired
	public void setProfileCompleteDAO(ProfileCompleteDAO profileCompleteDAO) {
		this.profileCompleteDAO = profileCompleteDAO;
	}

	@Autowired
	public void setSchoolDAO(SchoolDAO schoolDAO) {
		this.schoolDAO = schoolDAO;
	}

	@Autowired
	public void setUserSchoolDAO(UserSchoolDAO userSchoolDAO) {
		this.userSchoolDAO = userSchoolDAO;
	}

	@Autowired
	public void setMailService(MailServiceImpl mailService) {
		this.mailService = mailService;
	}

	@Autowired
	public void setActionLoggerService(ActionLoggerService actionLoggerService) {
		this.actionLoggerService = actionLoggerService;
	}
	
	@Autowired
	public void setPrivacySettingDAO(PrivacySettingDAO privacySettingDAO) {
		this.privacySettingDAO = privacySettingDAO;
	}

	public List<SchoolDTO> getUserSchools(String username){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<UserSchool> userSchools = user.getUserSchools();
			List<SchoolDTO> schools = new ArrayList<SchoolDTO>();
			for(UserSchool userSchool : userSchools){
				SchoolDTO dto = new SchoolDTO();
				dto.initFrom(userSchool.getSchool());
				dto.setFrom(userSchool.getFromYear());
				dto.setTo(userSchool.getToYear());
				schools.add(dto);
			}
			return schools;
		}catch (Exception e) {
			logger.error(e);
		}
		return null;
	}
	
	public List<CompanyDTO> getUserCompanies(String username){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<UserCompany> userCompanies = user.getUserCompanies();
			List<CompanyDTO> companies = new ArrayList<CompanyDTO>();
			for(UserCompany userCompany : userCompanies){
				CompanyDTO dto = new CompanyDTO();
				dto.initFrom(userCompany.getCompany());
				dto.setFromYear(userCompany.getFromYear());
				dto.setToYear(userCompany.getToYear());
				companies.add(dto);
			}
			return companies;
		}catch (Exception e) {
			logger.error(e);
		}
		return null;
	}
	
	public Map<String, String> getUserCategories(String username){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<Category> userCategories = user.getCategories();
			Map<String, String> categories = new HashMap<String, String>();
			for(Category category : userCategories){
				categories.put(category.getId().toString(), category.getCategory());
			}
			return categories;
		}catch (Exception e) {
			logger.error(e);
		}
		return null;
	}
	
	public Map<String, Map<String, String>> getUserHobbies(String username){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<Hobby> userHobbies = user.getHobbies();
			Map<String, Map<String, String>> result = new HashMap<String, Map<String,String>>();
			for(Hobby hobby : userHobbies){
				Map<String, String> hobbyMap = null;
				String category = hobby.getCategory();
				String name = hobby.getName();				
				if(result.containsKey(category)){
					hobbyMap = result.get(category);
				}else{
					hobbyMap = new HashMap<String, String>();
					result.put(category, hobbyMap);
				}
				hobbyMap.put(hobby.getId().toString(), name);				
			}
			
			return result;
		}catch (Exception e) {
			logger.error(e);
		}
		return null;
	}

	public UserInfoDTO getUserInfo(String username) {
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			UserInfo userInfoEntity = user.getUserInfo();
			UserInfoDTO dto = new UserInfoDTO();
			dto.initFrom(userInfoEntity);
			return dto;
		}catch (Exception e) {
			logger.equals(e);
		}
		return null;
	}
	
	public List<CityDTO> getAllCities(){
		List<City> cities = cityDAO.getAll();
		List<CityDTO> result = new ArrayList<CityDTO>();
		for(City city : cities){
			CityDTO dto = new CityDTO();
			dto.initFrom(city);
			if(!result.contains(dto)){
				result.add(dto);
			}
		}
		
		return result;
	}

	public int updateUserBasicInformation(String username, String firstName,
			String middleName, String lastName, Integer birthdayDate,
			Integer birthdayMonth, Integer birthdayYear, boolean isMale,
			String aboutMe, String email) {
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			if(true){
				user.setEmail(email);
				userDAOExt.save(user);
			}
			UserInfo userInfo = user.getUserInfo();
			userInfo.setFirstName(firstName);
			userInfo.setMiddleName(middleName);
			userInfo.setLastName(lastName);
			String birthdayStr = birthdayDate + "/" + birthdayMonth + "/" + birthdayYear;
			Date birthday = CalendarUtils.parseDate(birthdayStr, "dd/mm/yyyy");
			userInfo.setBirthday(birthday);
			userInfo.setAboutMe(aboutMe);
			userInfo.setMale(isMale);
			
			userInfoDAO.save(userInfo);
			return SaveResult.SUCCESS;
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;
	}

	public int updateUserContactInformation(String username, String yahoo,
			String skype, String facebook, String website, String mobile) {
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);

			UserInfo userInfo = user.getUserInfo();
			userInfo.setSkype(skype);
			userInfo.setYahoo(yahoo);
			userInfo.setFacebook(facebook);
			userInfo.setMobile(mobile);
			userInfo.setWebsite(website);
			
			userInfoDAO.save(userInfo);
			return SaveResult.SUCCESS;
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;
	}

	public int updateUserResidentInformation(String username, String address,
			String district, Long cityId) {
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);

			UserInfo userInfo = user.getUserInfo();
			userInfo.setAddress(address);
			userInfo.setDistrict(district);
			
			if(cityDAO.exists(cityId)){
				City city = cityDAO.get(cityId);
				userInfo.setCity(city);
			}
			
			userInfoDAO.save(userInfo);
			return SaveResult.SUCCESS;
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;
	}
	
	public int updateUserSchools(String username, SchoolDTO[] schools){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<UserSchool> userSchools = user.getUserSchools();
			if(userSchools != null)
				userSchoolDAO.deleteAll(userSchools);
			
			for(SchoolDTO school : schools){
				School schoolEntity = null;
				String schoolName = school.getName().trim();
				if(StringUtils.isBlank(schoolName))
					continue;
				
				Long cityId = school.getCityId();
				City city = cityDAO.get(cityId);
				List<School> entities = schoolDAO.getSchoolByNameAndCity(schoolName, null, cityId);
				if(entities.isEmpty()){
					schoolEntity = EntityFactory.createSchool(schoolName);
					schoolEntity.setCity(city);
					schoolEntity.setMainStudy(school.getMainStudy());
					schoolEntity.setTitle(school.getTitle());
					schoolEntity = schoolDAO.save(schoolEntity);
				}else{
					// assump only one
					schoolEntity = entities.get(0);
				}	
				
				UserSchool userSchool = EntityFactory.createUserSchool(user, schoolEntity);
				userSchool.setFromYear(school.getFrom());
				userSchool.setToYear(school.getTo());
				
				userSchoolDAO.save(userSchool);
			}
			
			return SaveResult.SUCCESS;
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;
	}
	
	public int updateUserCompanies(String username, CompanyDTO[] companies){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<UserCompany> userCompanies = user.getUserCompanies();
			if(userCompanies != null)
				userCompanyDAO.deleteAll(userCompanies);
			
			for(CompanyDTO company : companies){
				Company companyEntity = null;
				String companyName = company.getName().trim();
				if(StringUtils.isBlank(companyName))
					continue;
				
				Long cityId = company.getCityId();
				City city = cityDAO.get(cityId);
				List<Company> entities = companyDAO.getCompanyByNameAndCity(companyName, null, cityId);
				if(entities.isEmpty()){
					companyEntity = EntityFactory.createCompany(companyName);
					companyEntity.setCity(city);
					companyEntity.setField(company.getField());
					companyEntity.setTitle(company.getTitle());
					companyEntity = companyDAO.save(companyEntity);
				}else{
					// assump only one
					companyEntity = entities.get(0);
				}	
				
				UserCompany userCompany = EntityFactory.createUserCompany(user, companyEntity);
				userCompany.setFromYear(company.getFromYear());
				userCompany.setToYear(company.getToYear());
				
				userCompanyDAO.save(userCompany);
			}
			return SaveResult.SUCCESS;
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;
	}
	
	public int updateUserHobbies(String username, HobbyDTO[] hobbies){
		if(hobbies == null || hobbies.length == 0){
			return SaveResult.FAIL;
		}

		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			Set<Hobby> userHobbies = user.getHobbies();
			for(Hobby userHobby : userHobbies){
				userHobby.getUsers().remove(user);
				hobbyDAO.save(userHobby);
			}
			
			for(HobbyDTO hobby : hobbies){
				String name = hobby.getName().trim();
				String category = hobby.getCategory().trim();
				Hobby entity = hobbyDAO.getHobby(name, category);
				if(entity == null){
					hobbyDAO.createHobby(user, name, category);
				}else{
					entity.getUsers().add(user);
					hobbyDAO.save(entity);
				}
			}
			return SaveResult.SUCCESS;
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;
	}

	public Long register(UserDTO newUser) {
		try {
			if (StringUtils.isContainingUnicodeOrSpecialChar(newUser.getUsername()))
				return new Long(SaveResult.FAIL);

			if (StringUtils.hasSpaceInside(newUser.getUsername()))
				return new Long(SaveResult.FAIL);

			UserDTO registeredUser = saveUser(newUser);
			if(registeredUser == null){
				return new Long(SaveResult.FAIL);
			}
			
			User user = userDAOExt.get(registeredUser.getId());

			try {
				Role role = roleDAO.getRoleUser();
				UserRole userRole = EntityFactory.createUserRole(user, role);
				userRoleDAO.save(userRole);
				userInfoDAO.save(user.getUserInfo());

				actionLoggerService.log(user, user, ActionTypeEnum.REGISTER, ComponentEnum.USER_PROFILE, user.getId());

//				mailService.sendWelcome(newUser);
				mailService.sendActivationCode(newUser, user.getActivationCode());
			} catch (Exception e) {
				error("User role failed while registering", e);
			}

			return registeredUser.getId();
		} catch (UserExistsException e) {
			logger.error(e);
			return new Long(SaveResult.FAIL);
		}
	}
	
	public int activateAccount(String username, String activationCode){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			if(activationCode.equals(user.getActivationCode())){
				user.setAccountExpired(false);
				user.setEnabled(true);
				user.setCredentialsExpired(false);
				user = userDAOExt.save(user);
				
				UserDTO dto = new UserDTO();
				dto.initFrom(user);
				mailService.sendWelcome(dto);
				
				return SaveResult.SUCCESS;
			}
		}catch (Exception e) {
			logger.error(e);
		}
		return SaveResult.FAIL;	
	}
	
	public UserDTO saveUser(UserDTO userDTO) throws UserExistsException {
		if(passwordEncoder == null){
			logger.warn("passwordEncoder is not alive");
			return null;
		}
		
		String encryptedPassword = passwordEncoder.encodePassword(userDTO.getPassword(), null);
		String activationCode = getMD5Encode(userDTO.getUsername()+userDTO.getPassword()+"qwertasdgzxcv");
		User user = EntityFactory.createRegisterUser(userDTO, encryptedPassword, activationCode);

		try {
			user = userDAOExt.saveUser(user);			
		} catch (DataIntegrityViolationException e) {
			logger.warn(e.getMessage());
			throw new UserExistsException("User '" + user.getUsername() + "' already exists!");
		} catch (JpaSystemException e) { // needed for JPA
			logger.warn(e.getMessage());
			throw new UserExistsException("User '" + user.getUsername() + "' already exists!");
		}
		userDTO.initFrom(user);
		return userDTO;
	}
	
	private String getMD5Encode(String text) {

		byte[] defaultBytes = text.getBytes();
		try {
			MessageDigest algorithm = MessageDigest.getInstance("MD5");
			algorithm.reset();
			algorithm.update(defaultBytes);
			byte messageDigest[] = algorithm.digest();

			StringBuffer hexString = new StringBuffer();
			for (int i = 0; i < messageDigest.length; i++) {
				hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
			}
			return hexString.toString();
		} catch (NoSuchAlgorithmException nsae) {
			return null;
		}
	}
	
	public boolean isActivatedAccount(String username){
		try{
			User user = (User) userDAOExt.loadUserByUsername(username);
			return user.isEnabled();
		}catch (Exception e) {
			logger.error(e);
			return false;
		}
	}

}
